﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.Threading;
using Microsoft.Xna.Framework.Input;

namespace SettlersEngine.Exported
{
    public class GameInstance
    {
        private ContentControl m_Surface = null;
        private ServiceHost m_SinglePlayerHost = null;
        private BuildingGrid m_BuildingGrid;
        private List<IRaceConfig> m_Races = new List<IRaceConfig>();
        private ImageSource[] m_ResourceThumbnails = new ImageSource[(int)Resource.Max];

        internal Grid m_GameRenderPanel = new Grid();
        internal RuntimeMap Map { get; private set; }
        internal Guid SessionID { get; private set; }
        internal Guid GameID { get; private set; }
        internal GameSetup Setup { get; private set; }
        internal System.Drawing.Point CursorPosition { get; private set; }
        internal TranslateTransform WorldTransform { get; private set; }
        internal const Double POSITION_SCALE = 20;
        internal DChangeHandler<GameInstance, System.Drawing.Point> OnCursorChanged;

        public ReadOnlyCollection<IRaceConfig> Races { get; private set; }

        static GameInstance()
        {
            Keyboard.Initialize();
            AnimatedElement.LoadLibrary(new Uri("..\\Library\\", UriKind.Relative));
        }

        public void ShowBuildingGrid(int inBuildingTypeIndex)
        {
            m_BuildingGrid.Show(inBuildingTypeIndex);
        }

        public void HideBuildingGrid()
        {
            m_BuildingGrid.Hide();
        }

        public ImageSource GetThumbnailForBuilding(int inTypeIndex)
        {
            foreach(var race in Races)
            {
                foreach(var building in race.Buildables)
                {
                    if(building.TypeIndex == inTypeIndex)
                    {
                        return AnimatedElement.Library.FindClass(building.AnimationClass).AmbientSet.Animations[0].Frames[0].Source;
                    }
                }
            }

            return null;
        }

        public SelectionModel Selection { get; private set; }

        public ReadOnlyCollection<Resource> AllResources { get; private set; }
        /// <summary>
        /// The WPF <see cref="ContentControl"/> in which the game should be rendered.
        /// </summary>
        public ContentControl Surface
        {
            get
            {
                return m_Surface;
            }

            set
            {
                // detach from surface
                if (m_Surface != null)
                {
                    FrameworkElement composite = (FrameworkElement)m_Surface.Content;

                    composite.MouseMove -= Surface_MouseMove;
                    composite.MouseDown -= Surface_MouseDown;
                    m_BuildingGrid.UnregisterMouseHandlers(composite);

                    m_Surface.Content = null;
                }

                m_Surface = value;

                if (m_Surface != null)
                {
                    Grid composite = new Grid();

                    composite.ClipToBounds = true;
                    composite.Children.Add(m_GameRenderPanel);
                    composite.Children.Add(m_BuildingGrid);

                    composite.MouseMove += Surface_MouseMove;
                    composite.MouseDown += Surface_MouseDown;
                    m_BuildingGrid.RegisterMouseHandlers(composite);

                    m_Surface.Content = composite;
                }
            }
        }

        /// <summary>
        /// Represents the elapsed game time.
        /// </summary>
        public TimeSpan ElapsedTime { get; private set; }
        /// <summary>
        /// Is raised after all configured players called <see cref="RequestGameStart"/>. The game will
        /// start immediately and the GUI should set a <see cref="Surface"/>.
        /// </summary>
        public event DNotifyHandler<GameInstance> OnGameStarted;
        /// <summary>
        /// Is raised everytime connection to a player is lost. This has no impact as long as
        /// it is not the player of the current instance, in which case no further commands
        /// can be issued and received until reconnect is completed. If the connection is
        /// not reconnected within a reasonable amount of time (router restart), <see cref="OnPlayerAborted"/>
        /// is raised and the player is kicked from game.
        /// </summary>
        public event DNotifyHandler<GameInstance, Guid> OnPlayerDisconnected;
        /// <summary>
        /// Is raised when a player has finally lost his connection and failed to reconnect within
        /// a reasonable time. This can also indicate that the player just quit the game. In both cases
        /// the player has lost and all of his user-controlables will die immediately.
        /// </summary>
        public event DNotifyHandler<GameInstance, Guid> OnPlayerAborted;
        /// <summary>
        /// Is raised when a player is successfully reconnected after a disconnect.
        /// </summary>
        public event DNotifyHandler<GameInstance, Guid> OnPlayerConnected;
        /// <summary>
        /// Is raised when a player has joined the game.
        /// </summary>
        public event DNotifyHandler<GameInstance, Guid> OnPlayerJoined;


        public void Dump()
        {
            Map.Grid.SaveDebugState();
        }
        
        private static void ConnectToServer()
        {/*
            // create connection
            try
            {
                NetTcpBinding netBinding = new NetTcpBinding(SecurityMode.Transport, true);

                netBinding.ReliableSession.Ordered = true;

                
                EndpointAddress netEndpoint = new EndpointAddress(ServerAddress);
                //ChannelFactory<IGameHostService> channelFactory = new ChannelFactory<IGameHostService>(netBinding, netEndpoint);
                //IGameHostService connection = channelFactory.CreateChannel();

                GameHostServiceClient connection = new GameHostServiceClient(netBinding, netEndpoint);

                connection.Ping();

                return connection;
            }
            catch (Exception e)
            {
                throw new ArgumentException("Failed to connect to server at \"" + ServerAddress + "\".");
            }*/
        }

        /// <summary>
        /// </summary>
        /// <param name="inSetup"></param>
        /// <param name="inServerAddress"></param>
        /// <returns></returns>
        public static GameInstance CreateSinglePlayer(GameSetup inSetup)
        {
            GameInstance result = new GameInstance();

            // initialize session
            result.Setup = inSetup;

            // load race configs

            // create map
            result.Map = new RuntimeMap(result, 128, 128, new VisualObjectFactory(result));
            result.m_BuildingGrid = new BuildingGrid(result.Map);

            // register race configs
            IRaceConfig raceCfg = new RomanConfig();

            result.m_Races.Add(raceCfg);
            result.Map.RegisterBuildingConfig(raceCfg.Buildables.ToArray());


            return result;
        }


        private GameInstance()
        {
            WorldTransform = new TranslateTransform(0, 0);
            Races = new ReadOnlyCollection<IRaceConfig>(m_Races);
            Selection = new SelectionModel();
            AllResources = new ReadOnlyCollection<Resource>((from e in Enum.GetValues(typeof(Resource)).ToObjects() where (Resource)e != Resource.Max select (Resource)e).ToList());

            m_GameRenderPanel.MouseMove += Surface_MouseMove;

            Keyboard.OnKeyDown += new DKeyboardHandler(Keyboard_OnKeyDown);
            Keyboard.OnKeyUp += new DKeyboardHandler(Keyboard_OnKeyUp);
            Keyboard.OnKeyRepeat += new DKeyboardHandler(Keyboard_OnKeyRepeat);
        }

        void Keyboard_OnKeyDown(Keys inSender)
        {
            
        }

        void Keyboard_OnKeyUp(Keys inSender)
        {

        }

        void Keyboard_OnKeyRepeat(Keys inSender)
        {
            switch (inSender)
            {
                case Keys.Up: WorldTransform.Y += POSITION_SCALE; break;
                case Keys.Down: WorldTransform.Y -= POSITION_SCALE; break;
                case Keys.Left: WorldTransform.X += POSITION_SCALE; break;
                case Keys.Right: WorldTransform.X -= POSITION_SCALE; break;
            }

            if (WorldTransform.X > 0)
                WorldTransform.X = 0;

            if (WorldTransform.Y > 0)
                WorldTransform.Y = 0;

            if (WorldTransform.X <= -Map.Width * POSITION_SCALE)
                WorldTransform.X = -Map.Width * POSITION_SCALE - 1;

            if (WorldTransform.Y <= -Map.Height * POSITION_SCALE)
                WorldTransform.Y = -Map.Height * POSITION_SCALE - 1;

            Map.MoveScreen((int)(-WorldTransform.X / POSITION_SCALE), (int)(-WorldTransform.Y / POSITION_SCALE));

            m_BuildingGrid.Update();
        }

        private void Surface_MouseMove(object sender, MouseEventArgs e)
        {
            e.Handled = true;

            System.Drawing.Point backup = CursorPosition;
            Point mousePos = e.GetPosition(m_GameRenderPanel);

            CursorPosition = new System.Drawing.Point(
                Math.Max(0, Math.Min(Map.ScreenWidth - 1, (int)(mousePos.X / POSITION_SCALE))),
                Math.Max(0, Math.Min(Map.ScreenHeight - 1, (int)(mousePos.Y / POSITION_SCALE))));

            if (backup != CursorPosition)
            {
                if (OnCursorChanged != null)
                    OnCursorChanged(this, backup, CursorPosition);
            }
        }

        private void Surface_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point mousePos = e.GetPosition(m_GameRenderPanel);
            IPositionProvider obj = Map.Grid.GetObjectAtPosition(new System.Windows.Point(
                Math.Max(0, Math.Min(Map.ScreenWidth - 1, mousePos.X / POSITION_SCALE)),
                Math.Max(0, Math.Min(Map.ScreenHeight - 1, mousePos.Y / POSITION_SCALE))));

            if (obj is IBuildableObject)
            {
                IBuildableObject selected = obj as IBuildableObject;

                if (Selection.Building != null)
                {
                    if (Selection.Building.Buildable == selected)
                        return;
                }

                Selection.Building = new BuildingListener(selected);
            }
            else if (Selection.Building != null)
                Selection.Building = null;
        }

        public void Open()
        {

        }

        public void AddBuilding(System.Drawing.Point inPoint)
        {
        }

        internal void DoGameStarted()
        {
            if (OnGameStarted != null)
                OnGameStarted(this);
        }

        internal void DoPlayerJoined(Guid inPlayerID)
        {
            if (OnPlayerJoined != null)
                OnPlayerJoined(this, inPlayerID);
        }

        internal void DoPlayerConnected(Guid inPlayerID)
        {
            if (OnPlayerConnected != null)
                OnPlayerConnected(this, inPlayerID);
        }

        internal void DoPlayerDisconnected(Guid inPlayerID)
        {
            if (OnPlayerDisconnected  != null)
                OnPlayerDisconnected(this, inPlayerID);
        }

        internal void DoPlayerAborted(Guid inPlayerID)
        {
            if (OnPlayerAborted != null)
                OnPlayerAborted(this, inPlayerID);
        }

        public ImageSource GetThumbnailForResource(Resource inResource)
        {
            if (m_ResourceThumbnails.Contains(null))
            {
                // generate thumbnails
                foreach (var value in Enum.GetValues(typeof(Resource)))
                {
                    if ((Resource)value == Resource.Max)
                        continue;

                    Animation anim = AnimatedElement.Library.FindClass(value.ToString()).AmbientSet.Animations[0];

                    m_ResourceThumbnails[(int)value] = anim.Frames[0].Source;
                }

                if (m_ResourceThumbnails.Contains(null))
                    throw new ApplicationException("Enumeration \"Resource\" is not sequential.");
            }

            return m_ResourceThumbnails[(int)inResource];
        }
    }
}
